
/**
 * Baijiahulian.com Inc. Copyright (c) 2014-2016 All Rights Reserved.
 */

package org.xeustechnologies.jcl;

import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import lombok.extern.slf4j.Slf4j;

/**
 * @say little Boy, don't be sad.
 * @name Rezar
 * @time Sep 7, 2016
 * @Desc this guy is too lazy, nothing left.
 */
@Slf4j
public class ClassPathClassLoader extends JarClassLoader {

    private static ClassPathClassLoader INSTANCE = null;

    private Lock lock = new ReentrantLock();

    private ResourceClassLoader<?> loader;

    private ClassLoader superClassLoader = this.getClass().getClassLoader();

    public ClassPathClassLoader() {
        this(new CustomizeClassLoader());
    }

    public ClassPathClassLoader(ResourceClassLoader<?> loader) {
        this.setLoader(loader);
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.ClassLoader#loadClass(java.lang.String)
     */
    @Override
    public Class<?> loadClass(String className) throws ClassNotFoundException {
        return (loadClass(className, true));
    }

    /**
     * Overrides the loadClass method to load classes from other resources, JarClassLoader is the only subclass in this
     * project that loads classes from jar files
     *
     * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
     */
    @Override
    public Class<?> loadClass(String className, boolean resolveIt) throws ClassNotFoundException {
        if (className == null || className.trim().equals("")) {
            return null;
        }
        Class<?> clazz = null;
        if (!className.startsWith("java.lang")) {
            String key = className.replace(".", "/") + ".class";
            if (super.classes.containsKey(key)) {
                return super.classes.get(key);
            }
            lock.lock();
            byte[] resource = super.classpathResources.getResource(key);
            try {
                if (resource != null) {
                    if (super.classes.containsKey(key)) {
                        return super.classes.get(key);
                    }
                    this.loader.setParentLoader(this);
                    clazz = this.loader.loadClassWithResource(className, resource);
                    super.classes.put(key, clazz);
                }
            } catch (Exception ex) {

            } finally {
                lock.unlock();
            }
        }
        if (clazz == null) {
            log.info("super find : {} ", className);
            clazz = superClassLoader.loadClass(className);
        }
        if (clazz == null) {
            throw new ClassNotFoundException(className);
        }
        return clazz;
    }

    @Override
    public URL getResource(String name) {
        log.info("name is : {} ", name);
        URL resourceURL = super.getSystemResource(name);
        if (name.endsWith(".class")) {
            byte[] resource = super.classpathResources.getResource(name);
            if (resource == null) {
                String innerClassName = name.replace("/", ".").replaceAll(".class", "");
                this.add(resourceURL, innerClassName);
            }
        } else if (name.endsWith(".jar")) {
            // TODO
        }
        return resourceURL;
    }

    @Override
    protected Enumeration<URL> findResources(String name) throws IOException {
        return super.findResources(name);
    }

    /**
     * 根据资源定位符来获取到某个class的二进制数据，并进行加载
     * 
     * @param url
     * @param innerClassName
     */
    public void add(URL url, String innerClassName) {
        String packageName = innerClassName.substring(0, innerClassName.lastIndexOf("."));
        packageName = packageName.replace(".", "/");
        log.info("packageName is : {}  and Protocol is : {} ", packageName, url.getProtocol());

        if (url.getProtocol().equals("jar")) {
            classpathResources.loadClassFromClassPathJarUrl(url, innerClassName);
        } else if (url.getProtocol().equals("http")) {
            log.info("resource from internet : {} ", url);
            classpathResources.loadClassFromClassPathJarUrl(url, innerClassName);
        } else {
            classpathResources.loadResource(url, packageName);
        }
    }

    /**
     * @return the loader
     */
    public ResourceClassLoader<?> getLoader() {
        return loader;
    }

    /**
     * @param loader the loader to set
     */

    public void setLoader(ResourceClassLoader<?> loader) {
        this.loader = loader;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */

    @Override
    public String toString() {
        return "ClassPathClassLoader [Hello World!]";
    }

    private static Object lockForInstance = new Object();

    public CustomizeClassLoader newClassLoaderCondition() {
        if (this.loader instanceof CustomizeClassLoader) {
            return (CustomizeClassLoader) this.loader;
        } else {
            return new CustomizeClassLoader();
        }
    }

    public static ClassPathClassLoader newInstance() {
        if (INSTANCE == null) {
            synchronized (lockForInstance) {
                if (INSTANCE == null) {
                    // Enhancer enhancer = new Enhancer();
                    // enhancer.setSuperclass(ClassPathClassLoader.class);
                    // enhancer.setCallback(new MethodInterceptorImpl());
                    // INSTANCE = (ClassPathClassLoader) enhancer.create();
                    INSTANCE = new ClassPathClassLoader();
                }
            }
        }
        return INSTANCE;
    }

    // private static class MethodInterceptorImpl implements MethodInterceptor {
    //
    // public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    // log.info("method's name is : {} ", method.getName());
    // boolean isDefineClassMethod = isDefindClass(method);
    // if (isDefineClassMethod) {
    // if (isDefineClassMethod) {
    // log.info("is defineClass with classLoader:{}",
    // obj.getClass().getClassLoader().getClass().getName());
    // String className = String.valueOf(args[0]);
    // return ((ClassPathClassLoader) obj).loadClass(className);
    // }
    // } else if (method.getName().equals("loadClass")) {
    // String className = String.valueOf(args[0]);
    // log.info("load class is : {} ", className);
    // } else if (method.getName().equals("loadClassBytes")) {
    // String className = String.valueOf(args[0]);
    // log.info("loadClassBytes is : {} ", className);
    // } else if (method.getName().equals("formatClassName")) {
    // String className = String.valueOf(args[0]);
    // log.info("formatClassName is : {} ", className);
    // }
    // return proxy.invokeSuper(obj, args);
    // }
    //
    // /**
    // * @param method
    // * @return
    // */
    // private boolean isDefindClass(Method method) {
    // return method.getName().equals("defineClass");
    // }
    //
    // }

    public void updateClassLoader() {
        this.setLoader(new CustomizeClassLoader(this));
    }

    /**
     * @param oldClassName
     * @param bytecode
     */

}
